﻿/*                       NOTICE
 * 
 * This software was produced for the U. S. Government
 * under Contract No. FA8721-10-C-0001, and is
 * subject to the Rights in Noncommercial Computer Software
 * and Noncommercial Computer Software Documentation Clause
 * (DFARS) 252.227-7014 (JUN 1995)
 * 
 * Approved for Public Release: 11-2435
 * © 2011 The MITRE Corporation. All Rights Reserved. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GRIP
{
    #region BufferedFrame
    /// <summary>
    /// A simple storage class for holding the points in a contact frame. Contacts are classified and added to relevant lists.
    /// (NOTE: Since this is generic it should be moved into the Utils package and redocumented.)
    /// </summary>
    /// <typeparam name="T">The type of object to filter</typeparam>
    public class BufferedFrame<T>
    {
        /// <summary>A list of objects that have just entered the scene</summary>
        public List<T> NewObjects = new List<T>();

        /// <summary>A list of objects that were already in the scene</summary>
        public List<T> ExistingObjects = new List<T>();

        /// <summary>A list of objects that have just left the scene</summary>
        public List<T> RemovedObjects = new List<T>();

        /// <summary>A list of objects that are not yet in the scene</summary>
        public List<T> InactiveObjects = new List<T>();

        /// <summary>
        /// Clears the state holder's internal lists in preparation for a new frame.
        /// </summary>
        public void Clear()
        {
            NewObjects = new List<T>();
            ExistingObjects = new List<T>();
            RemovedObjects = new List<T>();
            InactiveObjects = new List<T>();
        }
    }
    #endregion

    #region BufferFilter
    /// <summary>
    /// BufferFilter acts as a temporal filter, maintaining a list of active objects based on what has previously been seen.
    /// It takes in lists of objects and sorts them into new, existing, removed, and inactive collections.
    /// If an object momentarily enters the scene but leaves too soon, it will be discarded as noise.
    /// Likewise, if an object leaves the scene temporarily but comes back quickly enough, the BufferFilter will assume it never left.
    /// </summary>
    /// <typeparam name="T">The type of object to track and filter</typeparam>
    public class BufferFilter<T>
    {
        #region Class Fields
        /// <summary>
        /// The time, in ticks (1/10000 millisecond), an object must consistently remain in the scene to be added.
        /// </summary>
        public int EntryBufferTime = 200 * 10000;

        /// <summary>
        /// The time, in ticks (1/10000 millisecond), an object must no longer be tracked in the scene to be removed.
        /// </summary>
        public int ExitBufferTime = 200 * 10000;

        /// <summary>
        /// A state map keeping track of each object's status. Entries corresponding to "true" are considered alive
        /// (they have been seen previously and already declared alive).
        /// </summary>
        protected Dictionary<T, bool> ActiveObjects = new Dictionary<T, bool>();

        /// <summary>
        /// A map of entry times for each object, used for deciding when to consider an object alive. Doubles as
        /// a map of exit times for vanishing objects.
        /// </summary>
        protected Dictionary<T, long> StartTimes = new Dictionary<T, long>();

        /// <summary>
        /// A list of objects that haven't been seen recently, and will be deleted once enough time has gone by.
        /// If the object comes back to life before time is up, it will be removed from this list without ever
        /// being considered removed.
        /// </summary>
        protected List<T> VanishingObjects = new List<T>();

        /// <summary>
        /// A list of all the objects present in the previous frame.
        /// </summary>
        protected List<T> PreviousObjects = new List<T>();

        /// <summary>
        /// A container for holding the results of various comparisons from the previous frame to the current one.
        /// </summary>
        protected SortedObjectHolder<T> SortedObjects = new SortedObjectHolder<T>();

        /// <summary>
        /// A container for holding the classifications of each object once the frame has been fully processed.
        /// </summary>
        protected BufferedFrame<T> Frame = new BufferedFrame<T>();
        #endregion

        #region Constructors
        /// <summary>
        /// Constructs a BufferFilter with the default entry and exit times (200ms each).
        /// </summary>
        public BufferFilter()
        {
        }

        /// <summary>
        /// Constructs a new BufferFilter with the desired entry and exit time.
        /// </summary>
        /// <param name="EntryBufferTime">The time, in milliseconds, an object must be consistently seen to be added</param>
        /// <param name="ExitBufferTime">The time, in milliseconds, an object must be consistently absent to be removed</param>
        public BufferFilter(int EntryBufferTime, int ExitBufferTime)
        {
            this.EntryBufferTime = EntryBufferTime * 10000;
            this.ExitBufferTime = ExitBufferTime * 10000;
        }
        #endregion

        #region Frame Processing
        /// <summary>
        /// Processes a frame of objects, classifying each one as New, Existing, Removed or Inactive.
        /// </summary>
        /// <param name="incomingObjects">The new frame of objects to classify</param>
        /// <returns>A set of object collections, based on classification</returns>
        public BufferedFrame<T> ProcessNewFrame(IEnumerable<T> incomingObjects)
        {
            Frame.Clear();

            ClassifyObjects(incomingObjects);
            ProcessNewObjects(SortedObjects.NewObjects);
            ProcessPendingObjects(SortedObjects.PendingObjects);
            ProcessRemovedObjects(SortedObjects.RemovedObjects);
            ProcessVanishingObjects();
            PreviousObjects = new List<T>(incomingObjects);

            return Frame;
        }

        public void ClearFilter()
        {
            ActiveObjects.Clear();
            StartTimes.Clear();
            VanishingObjects.Clear();
            PreviousObjects.Clear();
        }

        /// <summary>
        /// Processes all new objects that just appeared in this frame. If they're in the process of vanishing 
        /// (previously removed but not dead yet) the will come back to life; otherwise they are flagged as inactive
        /// until they've been seen long enough to become alive.
        /// </summary>
        /// <param name="newObjects">The collection of new objects that weren't seen in the previous frame</param>
        protected void ProcessNewObjects(IEnumerable<T> newObjects)
        {
            foreach (T newObject in newObjects)
            {
                if (VanishingObjects.Contains(newObject))
                {
                    VanishingObjects.Remove(newObject);
                    Frame.ExistingObjects.Add(newObject);
                }
                else
                {
                    StartTimes[newObject] = DateTime.Now.Ticks;
                    ActiveObjects[newObject] = false;
                    if (ActiveObjects == null)
                        throw new Exception("Active");
                    else if (newObject == null)
                        throw new Exception("newObj");
                    Frame.InactiveObjects.Add(newObject);
                }
            }
        }

        /// <summary>
        /// Processes objects that were seen in both the previous frame and this frame. If they're
        /// already marked as alive, it still calls them alive; if they aren't but the requisite time
        /// has passed, they are called new objects; otherwise they are marked as inactive until enough
        /// time has passed.
        /// </summary>
        /// <param name="pendingObjects">The collection of objects that have been seen before and are still seen now</param>
        protected void ProcessPendingObjects(IEnumerable<T> pendingObjects)
        {
            foreach (T pendingObject in pendingObjects)
            {
                if (ActiveObjects[pendingObject])
                    Frame.ExistingObjects.Add(pendingObject);
                else if (DateTime.Now.Ticks - StartTimes[pendingObject] > EntryBufferTime)
                {
                    ActiveObjects[pendingObject] = true;
                    Frame.NewObjects.Add(pendingObject);
                }
                else
                    Frame.InactiveObjects.Add(pendingObject);
            }
        }

        /// <summary>
        /// Adds the objects which were seen in the last frame, but not in this frame, to a list
        /// of "vanishing" objects. They will still be considered alive until they haven't been
        /// seen for the requisite amount of time.
        /// </summary>
        /// <param name="removedObjects">The collection of objects that disappeared this frame</param>
        protected void ProcessRemovedObjects(IEnumerable<T> removedObjects)
        {
            foreach (T removedObject in removedObjects)
            {
                if (ActiveObjects[removedObject])
                {
                    VanishingObjects.Add(removedObject);
                    StartTimes[removedObject] = DateTime.Now.Ticks;
                }
                else
                    Frame.InactiveObjects.Add(removedObject);
            }
        }

        /// <summary>
        /// Runs through the list of objects that haven't been seen recently, but haven't been
        /// officially killed yet. Depending on how long it's been for each one, it will either
        /// mark them as removed, or still existing (waiting for them to come back).
        /// </summary>
        protected void ProcessVanishingObjects()
        {
            for (int i = 0; i < VanishingObjects.Count; i++)
            {
                T vanishingObject = VanishingObjects[i];
                if (DateTime.Now.Ticks - StartTimes[vanishingObject] > ExitBufferTime)
                {
                    ActiveObjects[vanishingObject] = false;
                    VanishingObjects.RemoveAt(i);
                    i--;
                    Frame.RemovedObjects.Add(vanishingObject);
                }
                else
                    Frame.ExistingObjects.Add(vanishingObject);
            }
        }

        /// <summary>
        /// Compares the current frame to the previous one, classifying objects as either new,
        /// pending, or removed.
        /// </summary>
        /// <param name="incomingObjects">The current frame</param>
        protected void ClassifyObjects(IEnumerable<T> incomingObjects)
        {
            SortedObjects.NewObjects = incomingObjects.Except(PreviousObjects);
            SortedObjects.PendingObjects = incomingObjects.Intersect(PreviousObjects);
            SortedObjects.RemovedObjects = PreviousObjects.Except(incomingObjects);
        }
        #endregion

        #region SortedObjectHolder
        /// <summary>
        /// A container for holding pre-processed objects. They are sorted into new, pending, and removed lists
        /// by comparing the current frame to the previous frame.
        /// </summary>
        /// <typeparam name="U">The type of object to hold</typeparam>
        protected class SortedObjectHolder<U>
        {
            /// <summary>
            /// A collection of objects that are present in the current frame, but absent from the previous frame.
            /// </summary>
            public IEnumerable<U> NewObjects;

            /// <summary>
            /// A collection of objects that were present in both the previous and current frames.
            /// </summary>
            public IEnumerable<U> PendingObjects;

            /// <summary>
            /// A collection of objects that were present in the previous frame, but absent from the current frame.
            /// </summary>
            public IEnumerable<U> RemovedObjects;
        }
        #endregion
    }
    #endregion
}
